import * as THREE from '../build/three.module.js';
import {CSS3DRenderer,CSS3DObject} from './jsm/renderers/CSS3DRenderer.js'
import {TransformControls} from "./jsm/controls/TransformControls.js";
import {Loader} from "./Loader/Loader.js";
import {EditorControls} from "./EditorControls.js";
import {SetPositionCommand, SetRotationCommand, SetScaleCommand} from "./commands/Commands.js";
import {CameraView} from "./Editor.Camera.js";

var _DEFAULT_CAMERA = new THREE.PerspectiveCamera( 50, 1, 0.01, 30000 );
_DEFAULT_CAMERA.name = 'Camera';
_DEFAULT_CAMERA.position.set( 0, 5, 10 );
_DEFAULT_CAMERA.lookAt( new THREE.Vector3() );

function Editor() {
    var Signal = signals.Signal;

    this.signals = {
        windowResize: new Signal(),

        objectAdd: new Signal(),
        objectRemoved: new Signal(),
        objectSelected: new Signal(),
        objectFocused: new Signal(),
        objectChanged: new Signal(),

        cameraAdded: new Signal(),
        cameraRemoved: new Signal(),
        cameraResetted: new Signal(),

        helperAdded: new Signal(),
        helperRemoved: new Signal(),

        materialAdded: new Signal(),
        materialChanged: new Signal(),
        materialRemoved: new Signal(),

        geometryChanged: new Signal(),

        refreshObject3DUI: new Signal(),

        startPlay: new Signal(),
        stopPlay: new Signal(),

        editorCleared: new Signal(),

        sceneGraphChanged: new Signal(),
        viewportCameraChanged: new Signal(),
    };

    this.loader = new Loader(this);

    this.objects = [];
    this.geometries = {};
    this.materials = {};
    this.materialsRefCounter = new Map();
    this.textures = {};
    this.scripts = {};
    this.cameras = {};
    this.helpers = {};
    this.selected = null;

    this.scene = new THREE.Scene();

    this.sceneHelpers = new THREE.Scene();
    var sceneHelper = this.sceneHelpers;
    var scene = this.scene;
    var width = document.getElementById('container').offsetWidth;
    var height = document.getElementById('container').offsetHeight;
    const aspect = width / height;
    this.camera = _DEFAULT_CAMERA;
    this.camera.name = 'Camera';
    this.viewportCamera = this.camera;
    sceneHelper.add( new THREE.GridHelper( 1000, 10, 0x888888, 0x444444 ) );

    var box = new THREE.Box3();
    var selectionBox = new THREE.BoxHelper();
    selectionBox.material.depthTest = false;
    selectionBox.material.transparent = true;
    selectionBox.visible = false;
    sceneHelper.add(selectionBox);

    var objectPositionOnDown = null;
    var objectRotationOnDown = null;
    var objectScaleOnDown = null;

    const light = new THREE.DirectionalLight( 0xffffff, 1 );
    light.position.set( 1, 1, 1 );
    //告诉平行光需要开启阴影投射
    light.castShadow = true;
    scene.add( light );

    
    var container = document.getElementById('container');

    //add css3d renderer
    const labelRenderer = new CSS3DRenderer();
    labelRenderer.setSize( window.innerWidth, window.innerHeight );
    labelRenderer.domElement.style.position = 'absolute';
    labelRenderer.domElement.style.top = '0px';
    container.appendChild( labelRenderer.domElement );
    
    //add normal 3d renderer
    const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true});
    renderer.setClearColor( 0x000000, 0 );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize(width, height);
    renderer.domElement.style.position = 'absolute';
    renderer.domElement.style.top = 0;
    renderer.domElement.style["pointer-events"] = "none";
    container.appendChild(renderer.domElement);


    container.appendChild(new CameraView(this).dom);

    const geometry = new THREE.BoxGeometry(200, 200, 200);
    const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
    // const cube = new THREE.Mesh( geometry, material );
    // scene.add( cube );
    // this.objects.push( cube );

    this.camera.position.set( 1000, 500, 1000 );
    this.camera.lookAt( 0, 0, 0 );

    let scope = this;

    let transformControls = new TransformControls( this.camera, labelRenderer.domElement );
    transformControls.addEventListener( 'change', function(){
        var object = transformControls.object;

        if (object !== undefined) {
            selectionBox.setFromObject(object);
            var helper = scope.helpers[ object.id ];
            if ( helper !== undefined && helper.isSkeletonHelper !== true ) {
                helper.update();
            }
            scope.signals.refreshObject3DUI.dispatch( object );
        }
        render();
    } );

    transformControls.addEventListener('mouseDown', function () {
        console.log('transform : mousedown()');
        var object = transformControls.object;
        objectPositionOnDown = object.position.clone();
        objectRotationOnDown = object.rotation.clone();
        objectScaleOnDown = object.scale.clone();
        controls.enabled = false;

    });
    transformControls.addEventListener('mouseUp', function () {
        console.log('transform : mouseUp()');
        var object = transformControls.object;
        if (object !== undefined) {
            switch (transformControls.getMode()) {
                case 'translate':
                    if (!objectPositionOnDown.equals(object.position)) {
                        scope.execute(new SetPositionCommand(scope, object, object.position, objectPositionOnDown));
                    }
                    break;
                case 'rotate':
                    if (!objectRotationOnDown.equals(object.rotation)) {
                        scope.execute(new SetRotationCommand(scope, object, object.rotation, objectRotationOnDown));
                    }
                    break;
                case 'scale':
                    if (!objectScaleOnDown.equals(object.scale)) {
                        scope.execute(new SetScaleCommand(scope, object, object.scale, objectScaleOnDown));
                    }
                    break;
            }
        }
        controls.enabled = true;

    });

    sceneHelper.add(transformControls);

    var raycaster = new THREE.Raycaster();
    var mouse = new THREE.Vector2();
    var onDownPosition = new THREE.Vector2();
    var onUpPosition = new THREE.Vector2();
    var onDoubleClickPosition = new THREE.Vector2();

    const animate = function () {
        requestAnimationFrame( animate );

        render();

    };

    function render() {
        renderer.setViewport(0, 0, container.offsetWidth, container.offsetHeight);
        renderer.render(scene, scope.viewportCamera);
        renderer.autoClear = false;
        if (scope.camera === scope.viewportCamera) {
            renderer.render(sceneHelper, scope.camera);
            renderer.autoClear = true;
        }

        labelRenderer.render( scene, scope.viewportCamera );
    }



    function updateAspectRatio() {
        scope.camera.aspect = width / height;
        scope.camera.updateProjectionMatrix();
    }

    function getIntersects( point, objects ) {
        mouse.set( ( point.x * 2 ) - 1, - ( point.y * 2 ) + 1 );
        raycaster.setFromCamera( mouse, scope.camera );
        return raycaster.intersectObjects( objects )
            .filter( intersect => intersect.object.visible === true );
    }

    function getMousePosition( dom, x, y ) {
        var rect = container.getBoundingClientRect();
        return [ ( x - rect.left ) / rect.width, ( y - rect.top ) / rect.height ];
    }

    function handleClick() {
        if ( onDownPosition.distanceTo( onUpPosition ) === 0 ) {
            var intersects = getIntersects( onUpPosition, scope.objects );
            if ( intersects.length > 0 ) {
                var object = intersects[ 0 ].object;
                if ( object.userData.object !== undefined ) {
                    // helper
                    scope.select( object.userData.object );
                } else {
                    scope.select( object );
                }
            } else {
                scope.select( null );
            }
            render();
        }
    }

    function onMouseDown(event) {
        // event.preventDefault();
        console.log('onMousedown()');
        var array = getMousePosition(container, event.clientX, event.clientY);
        onDownPosition.fromArray(array);

        document.addEventListener('mouseup', onMouseUp, false);
    }

    function onMouseUp( event ) {
        var array = getMousePosition( container.dom, event.clientX, event.clientY );
        onUpPosition.fromArray( array );
        handleClick();
        document.removeEventListener( 'mouseup', onMouseUp, false );
    }

    function onMouseDblClick(event) {
        console.log('ondblclick()');
        var array = getMousePosition(container, event.clientX, event.clientY);
        onDoubleClickPosition.fromArray(array);
        var intersects = getIntersects(onDoubleClickPosition, scope.objects);
        if (intersects.length > 0){
            var intersect = intersects[0];
            scope.signals.objectFocused.dispatch(intersect.object);
        }
    }

    container.addEventListener( 'mousedown', onMouseDown, false );
    //container.addEventListener( 'pointerdown', onMouseClick, false );
    container.addEventListener('dblclick', onMouseDblClick, false);

    var controls = new EditorControls(this.camera, labelRenderer.domElement);//container);
    controls.addEventListener( 'change', function () {
        scope.signals.sceneGraphChanged.dispatch();
        scope.signals.refreshObject3DUI.dispatch( scope.camera );
    } );

    this.signals.windowResize.add(function() {
        var w = document.getElementById('container').offsetWidth;
        var h = document.getElementById('container').offsetHeight;
        renderer.setSize(w, h);
        labelRenderer.setSize(w,h);

        width = w;
        height = h;
        updateAspectRatio();
        console.log(width,height);
        render();
    })

    this.signals.objectAdd.add(function(object) {
        object.traverse(function (child){
            scope.objects.push(child);
        });

        render();
    })

    this.signals.objectRemoved.add(function(object){
        controls.enabled = true;
        if (object === transformControls.object) {
            transformControls.detach();
        }
        object.traverse(function (child){
            scope.objects.splice(scope.objects.indexOf(child), 1);
        })
    })

    this.signals.objectSelected.add(function(object){
        selectionBox.visible = false;
        transformControls.detach();
        if (object!==null && object!==scene && object!==scope.camera) {
            box.setFromObject( object );
            if ( box.isEmpty() === false ) {
                selectionBox.setFromObject( object );
                selectionBox.visible = true;
            }
            transformControls.attach( object );
        }
        render();
    })

    this.signals.sceneGraphChanged.add(function(){
        render();
    })

    this.signals.objectFocused.add(function (object){
        controls.focus(object);
    })

    this.signals.objectChanged.add(function(object){
        if (scope.selected === object){
            selectionBox.setFromObject(object);
        }
        if (object.isPerspectiveCamera){
            object.updateProjectionMatrix();
        }
        if (scope.helpers[object.id] !== undefined){
            scope.helpers[object.id].update();
        }
        render();
    })

    this.signals.helperAdded.add( function ( object ) {
        var picker = object.getObjectByName( 'picker' );
        if ( picker !== undefined ) {
            scope.objects.push( picker );
        }
    } );

    this.signals.helperRemoved.add( function ( object ) {
        var picker = object.getObjectByName( 'picker' );
        if ( picker !== undefined ) {
            scope.objects.splice( objects.indexOf( picker ), 1 );
        }
    } );

    this.signals.materialChanged.add(()=>{
        render();
    })

    this.signals.cameraResetted.add(updateAspectRatio);

    this.signals.editorCleared.add(()=>{
        controls.center.set(0,0,0);
        render();
    })

    this.signals.viewportCameraChanged.add(()=>{
        var viewportCamera = scope.viewportCamera;
        if (viewportCamera.isPerspectiveCamera) {
            viewportCamera.aspect = scope.camera.aspect;
            viewportCamera.projectionMatrix.copy( scope.camera.projectionMatrix );
        }else if (viewportCamera.isOrthographicCamera){
            //TODO
        }

        controls.enabled = (viewportCamera === scope.camera);
        render();
    })

    this.signals.geometryChanged.add((object)=>{
        if (object !== undefined){
            selectionBox.setFromObject(object);
        }
        render();
    })

    this.addCamera(this.camera);
    animate();
}

Editor.prototype={
    execute: function( cmd, optionalName){
        cmd.execute();
    },
    addObject: function(object, parent, index   ){
        var scope = this;
        object.traverse( function ( child ) {
            if ( child.geometry !== undefined ) scope.addGeometry( child.geometry );
            if ( child.material !== undefined ) scope.addMaterial( child.material );
            scope.addCamera( child );
            scope.addHelper( child );
        } );
        if ( parent === undefined ) {
            this.scene.add( object );
        } else {
            parent.children.splice( index, 0, object );
            object.parent = parent;
        }
        this.signals.objectAdd.dispatch(object);
    },
    addGeometry: function ( geometry ) {
        this.geometries[ geometry.uuid ] = geometry;
    },
    addMaterial: function ( material ) {
        if ( Array.isArray( material ) ) {
            for ( var i = 0, l = material.length; i < l; i ++ ) {
                this.addMaterialToRefCounter( material[ i ] );
            }
        } else {
            this.addMaterialToRefCounter( material );
        }
        this.signals.materialAdded.dispatch();
    },
    addMaterialToRefCounter: function ( material ) {
        var materialsRefCounter = this.materialsRefCounter;
        var count = materialsRefCounter.get( material );
        if ( count === undefined ) {
            materialsRefCounter.set( material, 1 );
            this.materials[ material.uuid ] = material;
        } else {
            count ++;
            materialsRefCounter.set( material, count );
        }
    },
    removeMaterial: function ( material ) {
        if ( Array.isArray( material ) ) {
            for ( var i = 0, l = material.length; i < l; i ++ ) {
                this.removeMaterialFromRefCounter( material[ i ] );
            }
        } else {
            this.removeMaterialFromRefCounter( material );
        }
        this.signals.materialRemoved.dispatch();
    },
    removeMaterialFromRefCounter: function ( material ) {
        var materialsRefCounter = this.materialsRefCounter;
        var count = materialsRefCounter.get( material );
        count --;
        if ( count === 0 ) {
            materialsRefCounter.delete( material );
            delete this.materials[ material.uuid ];
        } else {
            materialsRefCounter.set( material, count );
        }
    },
    removeObject: function(object){
        if (object.parent == null) return;
        var scope = this;
        object.parent.remove(object);
        this.signals.objectRemoved.dispatch(object);
        this.signals.sceneGraphChanged.dispatch();
    },
    select: function (object) {
        if (this.selected === object) return;
        var uuid = null;
        if (object !== null) {
            uuid = object.uuid;
        }
        this.selected = object;
        this.signals.objectSelected.dispatch(object);
    },
    deselect: function() {
        this.select(null);
    },
    addHelper: function () {
        var geometry = new THREE.SphereGeometry( 2, 4, 2 );
        var material = new THREE.MeshBasicMaterial( { color: 0xff0000, visible: false } );
        return function ( object, helper ) {
            if ( helper === undefined ) {
                if ( object.isCamera ) {
                    helper = new THREE.CameraHelper( object );
                } else if ( object.isPointLight ) {
                    helper = new THREE.PointLightHelper( object, 1 );
                } else if ( object.isDirectionalLight ) {
                    helper = new THREE.DirectionalLightHelper( object, 1 );
                } else if ( object.isSpotLight ) {
                    helper = new THREE.SpotLightHelper( object );
                } else if ( object.isHemisphereLight ) {
                    helper = new THREE.HemisphereLightHelper( object, 1 );
                } else if ( object.isSkinnedMesh ) {
                    helper = new THREE.SkeletonHelper( object.skeleton.bones[ 0 ] );
                } else {
                    // no helper for this object type
                    return;
                }
                var picker = new THREE.Mesh( geometry, material );
                picker.name = 'picker';
                picker.userData.object = object;
                helper.add( picker );
            }
            this.sceneHelpers.add( helper );
            this.helpers[ object.id ] = helper;
            this.signals.helperAdded.dispatch( helper );
        };

    }(),

    removeHelper: function ( object ) {
        if ( this.helpers[ object.id ] !== undefined ) {
            var helper = this.helpers[ object.id ];
            helper.parent.remove( helper );
            delete this.helpers[ object.id ];
            this.signals.helperRemoved.dispatch( helper );
        }
    },

    addCamera: function ( camera ) {
        if ( camera.isCamera ) {
            this.cameras[ camera.uuid ] = camera;
            this.signals.cameraAdded.dispatch( camera );
        }
    },

    getObjectMaterial: function (object, slot){
        var material = object.material;
        if (Array.isArray(material) && slot !== undefined) {
            material = material[slot];
        }
        return material;
    },

    setObjectMaterial: function (object, slot, newMaterial) {
        if (Array.isArray(object.material) && slot !== undefined){
            object.material[slot] = newMaterial;
        }
        else{
            object.material = newMaterial;
        }
    },

    setViewportCamera: function (uuid){
        this.viewportCamera = this.cameras[uuid];
        this.signals.viewportCameraChanged.dispatch();
    },

    clear: function() {
        this.camera.copy(_DEFAULT_CAMERA);
        this.signals.cameraResetted.dispatch();

        this.scene.name = 'Scene';
        this.scene.userData = {};
        this.scene.background = null;
        this.scene.environment = null;
        this.scene.fog = null;

        var objects = this.scene.children;

        while ( objects.length > 0 ) {
            this.removeObject( objects[0] );
        }

        this.geometries = {};
        this.materials = {};
        this.textures = {};
        this.scripts = {};

        this.materialsRefCounter.clear();

        // this.animations = {};
        // this.mixer.stopAllAction();

        this.deselect();

        this.signals.editorCleared.dispatch();
    },

    fromJSON: async function(json) {
        var loader = new THREE.ObjectLoader();
        var camera = await loader.parseAsync(json.camera);

        this.camera.copy(camera);
        this.signals.cameraResetted.dispatch();

        this.scripts = json.scripts;
        this.setScene(await loader.parseAsync(json.scene));
    },

    toJSON: function() {
        var scene = this.scene;
        var scripts = this.scripts;

        return {
            metadata : {},
            camera: this.camera.toJSON(),
            scene: this.scene.toJSON(),
            scripts: this.scripts,
        }
    },

    setScene: function (scene) {
        this.scene.uuid = scene.uuid;
        this.scene.name = scene.name;

        this.scene.background = scene.background;
        this.scene.fog = scene.fog;

        this.scene.userData = JSON.parse(JSON.stringify(scene.userData));

        // avoid render per object

        this.signals.sceneGraphChanged.active = false;

        while (scene.children.length > 0) {
            this.addObject(scene.children[0]);
        }

        this.signals.sceneGraphChanged.active = true;
        this.signals.sceneGraphChanged.dispatch();
    },
}

export{Editor}
